
#include "apue.h"
#include <sys/socket.h>

// size of control buffer to send/recv one file descriptor
#define CONTROLLEN CMSG_LEN(sizeof(int))

#ifdef LINUX
#define RELOP <
#else
#define RELOP !=
#endif

static struct cmsghdr *cmptr;

/*
    Receive a file descriptor from a server process. Also, any data
    received is passed to (*userfunc)(STDERR_FILENO, buf, nbytes).
    we have a 2-byte protocol for receiving the fd from send_fd()
*/
int recv_fd(int fd, ssize_t (*userfunc)(int, const void *, size_t))
{
    int newfd, nr;
    int status = -1;
    char *ptr = NULL;
    char buf[MAXLINE] = {0};
    struct iovec iov[1];
    struct msghdr msg;

    while (1)
    {
        iov[0].iov_base = buf;
        iov[0].iov_len = sizeof(buf);
        msg.msg_iov = iov;
        msg.msg_iovlen = 1;
        msg.msg_name = NULL;
        msg.msg_namelen = 0;

        if (NULL == cmptr && (cmptr = malloc(CONTROLLEN)) == NULL)
        {
            return -1;
        }

        msg.msg_control = cmptr;
        msg.msg_controllen = CONTROLLEN;
        if ((nr = recvmsg(fd, &msg, 0)) < 0)
        {
            err_ret("recvmsg error");
            return -1;
        }
        else if (0 == nr)
        {
            err_ret("connection closed by server");
            return -1;
        }

        /*
            See if this is the final data with null & status. Null
            is next to last byte of buffer; status byte is last byte;
            Zero status means there is a file descriptor to receive
        */
       for (ptr = buf; ptr < &buf[nr];)
       {
            if (0 == *ptr++)
            {
                if (ptr != &buf[nr-1])
                {
                    err_dump("message format error");
                }

                status = *ptr & 0xFF;
                if (0 == status)
                {
                    if (msg.msg_controllen RELOP CONTROLLEN)
                    {
                        err_dump("status = 0 buf no fd");
                    }
                    newfd = *(int *)CMSG_DATA(cmptr);
                }
                else
                {
                    newfd = -status;
                }
                nr = -2;
            }
       }

        if (nr > 0 && (*userfunc)(STDERR_FILENO, buf, nr) != nr)
        {
            return -1;
        }

        if (status >= 0)    // final data has arrived
        {
            return newfd;
        }
    }
}
